package distributedbank.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import distributedbank.Wrapper;
import distributedbank.server.messages.Acknowledgment;
import distributedbank.server.messages.HistoryRequest;
import distributedbank.server.messages.HistoryResponse;
import distributedbank.server.messages.Request;
import distributedbank.server.messages.Transaction;

/**
 * A BankBranch object represents a branch of the bank with a branchId. The
 * branch contains several accounts.
 */
public class BankBranch {
    private final int branchId;
    private final int backupId;

    public Map<Integer, Account> accounts = new HashMap<Integer, Account>();

    // merge these two structures into one class??
    private final List<Transaction> history = new ArrayList<Transaction>();

    // used to quickly check if a transaction has already been processed
    private final Set<Integer> transactionIdHistory = new HashSet<Integer>();

    public BankBranch(int branchId, int backupId) {
        this.branchId = branchId;
        this.backupId = backupId;
    }

    /**
     * Returns the Account object with the given accountId. If no account with
     * the given account id exists, a new account with zero balance will be
     * returned.
     */
    public Account getAccount(int accountId) {
        if (accounts.containsKey(accountId)) {
            return accounts.get(accountId);
        } else {
            // the requested account doesn't exist, so we create a new
            // account with zero balance.
            Account emptyAccount = new Account();
            accounts.put(accountId, emptyAccount);
            return emptyAccount;
        }
    }

    /**
     * Starts listening for connections on a socket on the specified port, and
     * handles each received message, sequentially.
     */
    public void runServer() throws IOException, ClassNotFoundException {
        Wrapper wrapper;
        try {
            wrapper = new Wrapper(this.branchId, this.backupId, false);
        } catch (IOException e) {
            System.out.println(e.getMessage());
            System.exit(1);
            return;
        }

        wrapper.waitForInitialOracleNotification();

        // if we're not part of the initial set of three backup servers, get the
        // history from the primary server!
        if (this.backupId > 3) {
            HistoryRequest request = new HistoryRequest(this.branchId,
                    wrapper.localPort);
            Acknowledgment response = wrapper.sendReliable(request);
            this.accounts = ((HistoryResponse) response).history;
        }

        // handle all received messages, sequentially
        while (true) {
            try {
                Request message = (Request) wrapper.receive();
                message.runRequest(this, wrapper);
            } catch (Exception e) {
                System.out.println("Error at branch " + branchId + ":");
                System.out.println(e.getMessage());
            }
        }
    }

    /**
     * USAGE: java distributedbank.server.BankBranch branchId
     * 
     * @throws ClassNotFoundException
     * @throws IOException
     */
    public static void main(String[] argv) throws IOException,
            ClassNotFoundException {
        if (argv.length != 2) {
            System.out
                    .println("Error: You must specify the branch ID and backup ID on the command-line.");
            System.exit(1);
        } else {
            BankBranch branch = new BankBranch(Integer.parseInt(argv[0]),
                    Integer.parseInt(argv[1]));
            branch.runServer();
        }
    }

    /**
     * Server Sync to the history list h.
     * 
     * @param h
     * @throws IOException
     */
    public void syncHistory(Transaction transaction) {
        this.history.add(transaction);
        transaction.runRequestSilent(this);
    }

    /**
     * Server Sync to the history list h.
     * 
     * @param h
     * @throws IOException
     */
    public void syncHistory(List<Transaction> h) throws IOException {
        // TODO
    }
}
